﻿using System;
using System.Text;
using System.Net;
using System.IO;
using FluorineFx;
using FluorineFx.IO;
using FluorineFx.Context;
using System.Net.Sockets;
using System.Security.Cryptography;


namespace Htxw.AMFRemoting
{
    /// <summary>
    /// 远程调用类
    /// </summary>
    public class RemoteObject
    {
        /// <summary>
        /// 通讯结果返回的数据
        /// </summary>
        private object[] _recObjArr;

        // version
        private char[] strVersion = { '0','1','0','0','0','0' };

        /// <summary>
        /// 远程端点：
        /// </summary>
        private readonly string _postUrl;

        private NetworkStream netStream;
        private Socket _remoteSock;
        private RemoteMethodArray _callList = new RemoteMethodArray();
        public ASObject _clientInfo;
        private BinaryWriter netWriter;
        private BinaryReader netReader;

        /// <summary>
        /// 构造函数
        /// </summary>
        public RemoteObject()
        {
            //初始化
            //_md5Length = 32;// HtConfig.Md5Length;
            //_postUrl = HtConfig.HttpPoint;
            //_encode = Encoding.GetEncoding("UTF-8");//HtConfig.HttpEncoding);
        }

        public RemoteObject(ASObject clientInfo)
        {
            _clientInfo = clientInfo;
            //初始化
           // _md5Length = 32; //HtConfig.Md5Length;
            //_postUrl = HtConfig.HttpPoint;
            //_encode = Encoding.GetEncoding("UTF-8");    //HtConfig.HttpEncoding);
        }

        //
        // host 主机
        // port 端口
        //
        public void Connect(string host, int port)
        {
            IPAddress[] adress = System.Net.Dns.GetHostAddresses(host);
            _remoteSock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            _remoteSock.Connect(adress, port);
            _remoteSock.NoDelay = true;
            _remoteSock.ReceiveTimeout = 3000;
            _remoteSock.SendTimeout = 3000;
            netStream = new NetworkStream(_remoteSock);
            netWriter = new BinaryWriter(netStream);
            netReader = new BinaryReader(netStream);
        }

        public void CloseConnect()
        {
            try
            {
                _remoteSock.Close();
            }
            catch (Exception e)
            {
            }
        }

        public void AddCall(string className, string funName, RemoteCallBack cb, Array funParams)
        {
            RemoteMethodObject method = new RemoteMethodObject(funName, className);
            method.SetParams(funParams);
            _callList.Add(method.GetRemoteMethodObject());
        }

        public void ClearCalls()
        {
            _callList = new RemoteMethodArray();
        }

        public object CallDirect(string className, string funName, Array funParams)
        {
            ClearCalls();
            AddCall(className, funName, null, funParams);
            object[] retArray = CommitByTCP();
            if (retArray.Length > 0)
            {
                ASObject ret = (ASObject)retArray[0];
                object code;
                if (!ret.TryGetValue("code", out code))
                    throw new Exception("the data format from server is error : [code]!");

                object retObject;
                if ((int)code == 0)
                {
                    if (!ret.TryGetValue("ret", out retObject))
                        throw new Exception("the data format from server is error : [ret]!");
                }
                else
                {
                    if (ret.TryGetValue("what", out retObject))
                        throw new Exception("error code : " + code + "], what : " + retObject);
                    throw new Exception("the data format from server is error : [what]!");
                }

                return retObject;
            }
            else
                throw new Exception("the result not Commit success!");
        }

        // 执行后，获取调用结果
        public object GetResult(int index)
        {
            return _recObjArr[index];
        }

        static uint m_nPackageCounter = 1;
        public uint CreatePacketCounter()
        {
            return m_nPackageCounter ++;
        }

        public object[] CommitByTCP()
        {
            try
            {
                // 发送数据... 将数据写入流 stream
                RpcSend(_clientInfo, _callList.GetArray());
                // 接收服务器返回的数据
                while (true)
                {
                    _recObjArr = GetRpcRecData(netStream);
                    if (null != _recObjArr)
                        return _recObjArr;
                }
            }
            catch (Exception e)
            {
                throw HtException.GetException(HtException.WsErrorMessageFormat, 80, e.Message, e.Source, e);
            }
        }

        //----------------------------------------------------------
        /// <summary>
        /// 向DS服务器发送请求，并返回结果
        /// </summary>
        /// <param name="callerList">请求参数列表</param>
        /// <param name="clientObject">客户端对象</param>
        /// <returns>通讯结果</returns>
        public object[] Send(Array callerList, ASObject clientObject)
        {
            try
            {
                //return Send2(callerList, clientObject);
                //HTTP请求对象
                var req = (HttpWebRequest)WebRequest.Create(_postUrl);

                //构造Http头（基本参数）
                req.Method = HtConfig.HttpMethod;
                req.ContentType = HtConfig.HttpContentType;
                req.SendChunked = HtConfig.HttpSendChunked;

                //   发送数据
                var stream = req.GetRequestStream();
                RpcSend(clientObject, callerList);
                stream.Close();

                //获得返回的Http流
                var rep = (HttpWebResponse)req.GetResponse();
                var receiveStream = rep.GetResponseStream();

                _recObjArr = GetRpcRecData(receiveStream);
                return _recObjArr;
            }
            catch (Exception e)
            {
                throw HtException.GetException(HtException.WsErrorMessageFormat, 80, e.Message, e.Source, e);
            }
        }

        private const int HEAD_SIZE = 32;
        private char[] crypt = { 'A','M','F','_','R','E','M','O','T','I','N','G','_','H','E','A','D' };
        static int crypt_len = 12;

        private byte[] CreateMD5(byte[] src, int offset, int len)
        {
            len -= 16;
            MD5 md5  = MD5.Create();
            MemoryStream stm = new MemoryStream ((int)(src.Length + crypt_len));
            StreamWriter writer = new StreamWriter(stm);
            writer.Write(crypt, 0, crypt_len);
            writer.Flush();
            stm.Write(src, offset, len);
            return md5.ComputeHash(stm.GetBuffer(), 0, crypt_len + len);
        }

        private byte[] GetRpcHeader(uint len, uint counter, byte cmd, byte tag)
        {
            /* 4 + 4 + 2 + 6 + 16 = 32
            uint32 len;					// 数据包长度，含长度
            uint32 count;				// 包号，当有多个包同时返回时,用来识别请求与返回包的关系，同时起到加密混淆的作用
            byte cmd;					// 包的指令类型:
			            //#define PACKET_CMD_BREAK		0	// 断开连接 
			            //#define PACKET_CMD_RPC		1	// 远程调用请求包
			            //#define PACKET_CMD_RET		2	// 远程调用返回包
			            //#define PACKET_CMD_EVENT		3	// 事件通知
			            //#define PACKET_CMD_MESSAGE	4	// 实时消息
            char strVersion[6];			// 版本号
            unsigned char md5[16];		// md5 包头校验
            */
            MemoryStream stm = new MemoryStream();
            BinaryWriter writer = new BinaryWriter(stm);
            writer.Write(len);
            writer.Write(counter);
            writer.Write(cmd);
            writer.Write(tag);
            writer.Write(strVersion, 0, 6);

            byte[] fromData = stm.GetBuffer();
            byte[] targetData = CreateMD5(fromData, 0, HEAD_SIZE);
            
            writer.Write(targetData, 0, 16);
            return stm.GetBuffer();
        }

        // read header, check by md5 
        // if ok, return the header memoryStream
        // else return null;
        private byte[] ReadHeader (Stream receiveStream)
        {
            byte[] head = new byte[HEAD_SIZE];
            if (receiveStream.Read(head, 0, HEAD_SIZE) == HEAD_SIZE)
            {
                MemoryStream stm = new MemoryStream(head, false);
                byte[] targetData = CreateMD5(head, 0, HEAD_SIZE);
                for (int i=0; i<16; i++)
                {
                    if (targetData[i] != head[i + 16])
                        throw HtException.GetException(HtException.WsErrorMessageFormat, 8818, "数据格式不正确, iLen < 0", "getRequestObject( Stream )");  // MD5校验失败
                }
                return head;   
            }
            else
                return null;
        }

        const byte PACKET_CMD_BREAK = 0;	    // 0 断开连接 
        const byte PACKET_CMD_RPC = 1;    	// 1 远程调用请求包
        const byte PACKET_CMD_RET = 2;	    // 2 远程调用返回包
        const byte PACKET_CMD_EVENT = 3;	    // 3 事件通知
        const byte PACKET_CMD_MESSAGE = 4;	// 4 实时消息
        const byte TAG_HAS_CLIENT_INFO = 0X01;  // 包含 clientInfo

        /// <summary>
        /// 设置并发送AMF数据
        /// </summary>
        /// <param name="headerData">协议头</param>
        /// <param name="clientObject">扩展数据头</param>
        /// <param name="callerList">调用请求列表</param>
        /// <param name="stream">HTTP的数据流</param>
        private void RpcSend(ASObject clientObject, Array callerList)
        {
            // 准备要写入的clientInfo, Amf对象
            MemoryStream mmStream = new MemoryStream();
            var amfWriter = new AMFWriter(mmStream);
            if (null == clientObject)
                clientObject = new ASObject();
            byte tag = 0;
            if (null != clientObject)
            {
                amfWriter.WriteAMF3Data(clientObject);
                tag |= TAG_HAS_CLIENT_INFO;
            }
            amfWriter.WriteAMF3Data(callerList);
            amfWriter.Flush();

            //准备要发送的数据头
            uint counter = this.CreatePacketCounter();
            byte[] header = GetRpcHeader((uint)mmStream.Length + HEAD_SIZE, counter, PACKET_CMD_RPC, tag);

            // 发送到网络
            netStream.Write(header, 0, HEAD_SIZE);
            netStream.Write(mmStream.GetBuffer(), 0, (int)mmStream.Length);
            netStream.Flush();
        }

        /// <summary>
        /// 验证返回的数据的有效性，并取出数据
        /// </summary>
        /// <param name="receiveStream">HTTP的返回数据流</param>
        /// <returns>返回的数据</returns>
        private object[] GetRpcRecData(Stream receiveStream)
        {
            try
            {
                byte[] head = ReadHeader (receiveStream);
                if (null == head)
                    throw HtException.GetException(HtException.WsErrorMessageFormat, 8818, "数据格式不正确, 数据加密校验不通过；", "getRequestObject( Stream )");

                MemoryStream stm = new MemoryStream(head, false);
                BinaryReader reader = new BinaryReader(stm);
                
                uint len = reader.ReadUInt32();
                uint counter= reader.ReadUInt32();
                byte cmd = reader.ReadByte();
                byte tag = reader.ReadByte();
                char[] strVersion = reader.ReadChars(6);

            	int iLen = (int)len - 32;
                if (iLen < 0)
                    throw HtException.GetException(HtException.WsErrorMessageFormat, 8818, "数据格式不正确, iLen < 0", "getRequestObject( Stream )");

	            if (iLen == 0)
		            return null;   // 没有数据段

                byte[] buf = new byte[iLen];
                receiveStream.Read(buf, 0, iLen);
                MemoryStream objStm = new MemoryStream(buf, false);
                //AMF读取器
                AMFReader tmpAMFReader = new AMFReader(objStm);
                //读取ClientInfo
                ASObject tmpReClientInfo;
                if ((tag & TAG_HAS_CLIENT_INFO) > 0)
                    tmpReClientInfo = (ASObject)tmpAMFReader.ReadAMF3Data();

                // 读取数据
                object tmpReData = tmpAMFReader.ReadAMF3Data();
                //把强制转换为对象数组
                object[] tmpReArray = tmpReData as object[];
                switch (cmd)
                {
                    case PACKET_CMD_RET:
                        {// 远程调用返回
                            return tmpReArray;
                        }
                    case PACKET_CMD_EVENT:
                        {// 发送事件
                            return null;
                        }
                    default:
                        throw HtException.GetException(HtException.WsErrorMessageFormat, 999, "服务器返回的协议不正确，无效的CMD，强制断开连接", "getRequestObject( Stream )");
                }
            }
            catch (Exception ex)
            {
                throw HtException.GetException(HtException.WsErrorMessageFormat, 999, "服务器返回的协议不正确，强制断开连接" + ex.Message, ex.Source, ex);
            }
        }
        //----------------------------------------------------------
    }
}
